15143
4921
Python中yield關鍵字的用途是什麼?
例如,我試圖理解此代碼1:
def _get_child_candidates(自身,距離,min_dist,max_dist):
如果self._leftchild和距離-max_dist  = self._median:
產生self._rightchild
這是呼叫者:
結果,候選人= [],[自身]
而候選人:
節點=候選人.pop()
距離= node._get_dist(obj)
如果距離<= max_dist和距離> = min_dist:
result.extend(node._values)
候選人擴展(node._get_child_candidates(距離,min_dist,max_dist))
返回結果
調用_get_child_candidates方法時會發生什麼?
是否返回列表?一個元素?再叫一次嗎?後續通話什麼時候停止?
1.這段代碼是由Jochen Schulz(jrschulz)編寫的,Jochen Schulz是一個很好的用於度量空間的Python庫。這是完整源代碼的鏈接:模塊mspace。 
1個
2
下一個
要了解產量,您必須了解發電機是什麼。而且,在您了解生成器之前,您必須了解可迭代。
可迭代
創建列表時,可以一一閱讀它的項目。逐一讀取其項稱為迭代:
>>> mylist = [1、2、3]
>>>對於我在我的列表中:
...打印(i)
1個
2
3
mylist是可迭代的。當您使用列表推導時,您將創建一個列表,因此是可迭代的:
>>> mylist = [x * x for x in range(3)]
>>>對於我在我的列表中:
...打印(i)
1個
4
您可以在“ for ... in ...”上使用的所有內容都是可迭代的;列表,字符串,文件...
這些可迭代的方法很方便,因為您可以隨意讀取它們,但是您將所有值都存儲在內存中,當擁有很多值時,這並不總是想要的。
發電機
生成器是迭代器,一種只能迭代一次的可迭代器。生成器未將所有值存儲在內存中,而是即時生成值:
>>> mygenerator =(x * x表示範圍(3)中的x)
>>>對於mygenerator中的我:
...打印(i)
1個
4
除了使用()而不是[]之外,其他功能相同。但是,您不能第二次在mygenerator中執行i,因為生成器只能使用一次:它們先計算0,然後忘記它,然後計算1,最後一次計算4。
產量
yield是一個與return一樣使用的關鍵字,只是該函數將返回生成器。
>>> def createGenerator():
...我的清單=範圍(3)
...對於我在我的清單中:
...產生i * i
...
>>> mygenerator = createGenerator()#創建一個生成器
>>> print(mygenerator)#mygenerator是一個對象!
<生成器對象createGenerator位於0xb7555c34>
>>>對於mygenerator中的我:
...打印(i)
1個
4
這是一個無用的示例,但是當您知道函數將返回只需要讀取一次的大量值時,它就很方便。
要掌握yield,必須了解當調用函數時,在函數主體中編寫的代碼不會運行。該函數僅返回生成器對象,這有點棘手:-)
然後,您的代碼將在每次使用生成器時都從停止的地方繼續。
現在最困難的部分是:
for第一次調用從您的函數創建的生成器對象時,它將從頭開始運行函數中的代碼,直到達到yield為止,然後它將返回循環的第一個值。然後,每個後續調用將運行您在函數中編寫的循環的另一次迭代,並返回下一個值。這將一直持續到生成器被認為是空的為止,這種情況會在函數運行而沒有達到產量時發生。那可能是因為循環已經結束,或者是因為您不再滿足“ if / else”的要求。
您的代碼說明
發電機:
#在這裡創建節點對象的方法,該方法將返回生成器
def _get_child_candidates(自身,距離,min_dist,max_dist):
#這是每次使用generator對象時將被調用的代碼:
#如果節點對象的左邊還有一個子對象
#AND如果距離合適,則返回下一個孩子
如果self._leftchild和距離-max_dist  = self._median:
產生self._rightchild
#如果函數到達此處,生成器將被視為空
#不超過兩個值:左子代和右子代
呼叫者:
#創建一個空列表和一個包含當前對象引用的列表
結果,候選人= list(),[個體]
#循環候選(它們在開始時只包含一個元素)
而候選人:
#獲取最後一個候選人並將其從列表中刪除
節點=候選人.pop()
#獲取obj和候選對象之間的距離
距離= node._get_dist(obj)
#如果距離合適,則可以填寫結果
如果距離<= max_dist和距離> = min_dist:
result.extend(node._values)
#在候選人列表中添加候選人的孩子
#因此循環將一直運行,直到看起來為止
#在候選人的子女等的所有子女中
候選人擴展(node._get_child_candidates(距離,min_dist,max_dist))
返回結果
此代碼包含幾個智能部分:
循環在一個列表上迭代,但是循環在迭代時列表會擴展:-)這是瀏覽所有這些嵌套數據的一種簡潔方法,即使這樣做有點危險,因為您可能會遇到無限循環。在這種情況下,candidates.extend(node._get_child_candidates(distance,min_dist,max_dist))耗盡了生成器的所有值,但同時仍在繼續創建新的生成器對象,該對象將產生與先前值不同的值,因為它沒有應用於相同的值節點。
extend()方法是一個列表對象方法,該方法需要可迭代並將其值添加到列表中。
通常我們將一個列表傳遞給它:
>>>一個= [1、2]
>>> b = [3,4]
>>> a.extend(b)
>>>打印(a)
[1、2、3、4]
但是在您的代碼中,它得到了一個生成器,這很好,因為:
您不需要兩次讀取值。
您可能有很多孩子,並且您不希望所有孩子都存儲在內存中。
它之所以有效,是因為Python不在乎方法的參數是否為列表。 Python期望可迭代,因此它將與字符串,列表,元組和生成器一起使用!這就是所謂的鴨子輸入,這是Python如此酷的原因之一。但這是另一個故事,還有另一個問題...
您可以在這裡停止,或者閱讀一點以了解生成器的高級用法:
控制發電機耗盡
>>>類Bank():#讓我們創建一個銀行,構建ATM
...危機=錯誤
... def create_atm(self):
...雖然不是自我危機:
...產生“ $ 100”
>>> hsbc = Bank()#一切正常時,ATM會為您提供所需的一切
>>> corner_street_atm = hsbc.create_atm()
>>>打印(corner_street_atm.next())
$ 100
>>>打印(corner_street_atm.next())
$ 100
>>>打印([[corner_street_atm.next()表示範圍(5)中的現金])
['$ 100','$ 100','$ 100','$ 100','$ 100']
>>> hsbc.crisis = True#危機來了,沒有錢了!
>>>打印(corner_street_atm.next())
<類型'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm()#對於新的ATM機甚至如此
>>>打印(wall_street_atm.next())
<類型'exceptions.StopIteration'>
>>> hsbc.crisis = False#問題是,即使在危機後,ATM仍然為空
>>>打印(corner_street_atm.next())
<類型'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm()#建立一個新的以恢復營業
>>>兌換brand_new_atm:
...打印現金
$ 100
$ 100
$ 100
$ 100
$ 100
$ 100
$ 100
$ 100
$ 100
...
注意:對於Python 3,請使用print(corner_street_atm .__ next __())或print(next(corner_street_atm))
對於諸如控制對資源的訪問之類的各種事情,它可能很有用。
Itertools,您最好的朋友
itertools模塊包含用於操縱可迭代對象的特殊功能。曾經希望復制一個發電機嗎?
連鎖兩個發電機?用一個班輪將值嵌套在嵌套列表中?地圖/郵編而未創建其他列表?
然後只需導入itertools。
一個例子?讓我們看一下四馬比賽的可能到達順序:
>>>馬= [1、2、3、4]
>>>種族= itertools.permutations(馬)
>>>打印(種族)
<位於0xb754f1dc的itertools.permutations對象>
>>>打印(列表(itertools.permutations(馬)))
[(1、2、3、4),
(1,2,4,3),
(1、3、2、4),
(1、3、4、2),
(1、4、2、3),
(1、4、3、2),
(2,1,3,4),
(2,1,4,3),
(2,3,1,4),
(2,3,4,1),
(2,4,1,3),
(2,4,3,1),
(3,1,2,4),
(3,1,4,2),
(3,2,1,4),
(3,2,4,1),
(3,4,1,2),
(3,4,2,1),
(4,1,2,3),
(4,1,3,2),
(4,2,1,3),
(4,2,3,1),
(4、3、1、2),
(4、3、2、1)]
了解迭代的內部機制
迭代是一個包含可迭代對象(實現__iter __()方法)和迭代器(實現__next __()方法)的過程。
可迭代對像是可以從中獲取迭代器的任何對象。迭代器是使您可以迭代可迭代對象的對象。
本文中有更多關於for循環如何工作的內容。
|
了解產量的捷徑
當您看到帶有yield語句的函數時,請應用以下簡單技巧,以了解將要發生的情況:
在函數的開頭插入一行結果= []。
將每個yield expr替換為result.append(expr)。
在函數的底部插入行返回結果。
是的-沒有更多的收益聲明!閱讀並找出代碼。
將功能與原始定義進行比較。
這個技巧可能會讓您對函數背後的邏輯有所了解,但是yield實際發生的事情與基於列表的方法發生的事情顯著不同。在許多情況下,yield方法也將大大提高內存效率和速度。在其他情況下,即使原始函數運行正常,此技巧也會使您陷入無限循環。請繼續閱讀以了解更多信息...
不要混淆您的Iterable,Iterators和Generators
首先,迭代器協議-當您編寫時
對於mylist中的x:
圈體
Python執行以下兩個步驟:
獲取mylist的迭代器:
調用iter(mylist)->這將返回帶有next()方法的對象(或Python 3中的__next __())。
[這是大多數人忘記告訴您的步驟]
使用迭代器遍歷項目:
繼續在步驟1返回的迭代器上調用next()方法。將next()的返回值分配給x,並執行循環體。如果從next()內部引發異常StopIteration,則意味著迭代器中沒有更多值,並且退出了循環。
事實是Python隨時想要遍歷對象的內容時都執行上述兩個步驟-因此它可能是for循環,但也可能是諸如otherlist.extend(mylist)之類的代碼(其中otherlist是Python列表) 。
這裡的mylist是可迭代的,因為它實現了迭代器協議。在用戶定義的類中,可以實現__iter __()方法以使您的類的實例可迭代。此方法應返回迭代器。迭代器是具有next()方法的對象。可以在同一個類上同時實現__iter __()和next(),並使__iter __()返回self。這將在簡單情況下起作用,但是當您希望兩個迭代器同時在同一個對像上循環時則無效。
這就是迭代器協議,許多對像都實現了該協議:
內置列表,字典,元組,集合,文件。
用戶定義的實現__iter __()的類。
發電機。
請注意,for循環不知道它要處理的是哪種對象-它僅遵循迭代器協議,並且很高興在調用next()時逐項獲取對象。內置列表一一返回其項,字典一一返回鍵,文件一一返回行,依此類推。生成器返回...產生收益的地方是:
def f123():
產量1
產量2
產量3
對於f123()中的項目:
打印項目
如果您在f123()中只有三個return語句,則不會執行yield語句,只有第一個會執行,該函數將退出。但是f123()不是普通函數。調用f123()時,它不會返回yield語句中的任何值!它返回一個生成器對象。另外,該函數並沒有真正退出-進入了掛起狀態。當for循環嘗試循環生成器對象時,該函數從先前返回的yield之後的下一行開始從其掛起狀態恢復,執行下一行代碼(在本例中為yield語句),並返回作為下一項。這會一直發生,直到函數退出,此時生成器將引發StopIteration,然後循環退出。
因此,生成器對像有點像適配器-一端通過公開__iter __()和next()方法來保持for循環快樂,從而展示了迭代器協議。但是,在另一端,它僅運行該函數以從中獲取下一個值,然後將其放回暫停模式。
為什麼使用發電機?
通常,您可以編寫不使用生成器但實現相同邏輯的代碼。一種選擇是使用我之前提到的臨時列表“技巧”。並非在所有情況下都可行,例如如果您有無限循環,或者當列表很長時,它可能會導致內存使用效率低下。另一種方法是實現一個新的可迭代類SomethingIter,該類將狀態保留在實例成員中,並在next()(或Python 3中的__next __())方法中執行下一個邏輯步驟。根據邏輯的不同,next()方法內部的代碼可能看起來非常複雜並且容易出現錯誤。在這裡,發電機提供了一種干淨而簡單的解決方案。
|
這樣想:
迭代器只是一個具有next()方法的對象的美化名詞。因此,由yield產生的函數最終是這樣的:
原始版本:
def some_function():
對於我在xrange(4)中:
讓我
對於我在some_function()中:
打印我
這基本上是Python解釋器對上面的代碼所做的:
分類:
def __init __():
#從-1開始,以便在下面加1時得到0。
self.count = -1
#__iter__方法將被“ for”循環調用一次。
#其餘的魔術發生在此方法返回的對像上。
#在這種情況下,它是對象本身。
def __iter __(自己):
返回自我
#下一個方法將被'for'循環重複調用
#直到引發StopIteration。
def next(self):
self.count + = 1
如果self.count <4:
返回self.count
其他:
#引發StopIteration異常
#表示迭代器已完成。
#這被“ for”循環隱式捕獲。
提高StopIteration
def some_func():
把它返還()
對於我在some_func()中:
打印我
要了解幕後發生的事情,可以將for循環重寫為:
迭代器= some_func()
嘗試:
而1:
打印iterator.next()
StopIteration除外:
通過
這樣做更有意義還是會讓您更加困惑? :)
我應該指出,出於說明目的,這過於簡單了。 :)
|
yield關鍵字簡化為兩個簡單事實:
如果編譯器在函數內部的任何位置檢測到yield關鍵字,則該函數不再通過return語句返回。相反,它立即返回一個懶惰的“待處理列表”對象,稱為生成器
生成器是可迭代的。什麼是可迭代的?就像列表,集合,範圍或字典視圖一樣,它具有用於以特定順序訪問每個元素的內置協議。
簡而言之:生成器是一個懶惰的,增量待定的列表,並且yield語句允許您使用函數符號來編程生成器應逐漸吐出的列表值。
generator = myYieldingFunction(...)
x =列表(發電機)
發電機
v
[x [0],...,???]
發電機
v
[x [0],x [1],...,???]
發電機
v
[x [0],x [1],x [2],...,???]
StopIteration例外
[x [0],x [1],x [2]]完成
列表== [x [0],x [1],x [2]]
例
讓我們定義一個函數makeRange,就像Python的range一樣。調用makeRange(n)返回一個生成器:
def makeRange(n):
#返回0,1,2,...,n-1
我= 0
而我>> makeRange(5)
<生成器對象makeRange位於0x19e4aa0>
要強制生成器立即返回其待處理的值,可以將其傳遞到list()中(就像您可以進行任何迭代一樣):
>>>列表(makeRange(5))
[0,1,2,3,4]
將示例與“僅返回列表”進行比較
可以將上面的示例視為僅創建一個列表,並將其附加並返回:
#list-version##generator-version
def makeRange(n):#def makeRange(n):
“”“返回[0,1,2,...,n-1]”“”#〜“”“返回0,1,2,...,n-1”“”
TO_RETURN = []#>
i = 0#i = 0
當我
>>> makeRange(5)
[0,1,2,3,4]
但是,有一個主要區別。請參閱最後一節。
您如何使用發電機
可迭代是列表理解的最後一部分,並且所有生成器都是可迭代的,因此經常像這樣使用它們:
#_ITERABLE_
>>> [x + 10 for makeRange(5)中的x]
[10、11、12、13、14]
為了更好地了解生成器,可以使用itertools模塊(一定要使用chain.from_iterable,而不是有保證的情況下使用chain)。例如,您甚至可以使用生成器來實現無限長的惰性列表,例如itertools.count()。您可以實現自己的def enumerate(iterable):zip(count(),iterable),或者在while循環中使用yield關鍵字來實現。
請注意:生成器實際上可以用於更多事情,例如實現協程或不確定性編程或其他優雅的事情。但是,我在這裡提出的“惰性列表”觀點是您會發現的最常見用法。
幕後花絮
這就是“ Python迭代協議”的工作方式。也就是說,執行list(makeRange(5))時發生了什麼。這就是我之前所說的“懶惰的增量列表”。
>>> x = iter(範圍(5))
>>>下一個(x)
>>>下一個(x)
1個
>>>下一個(x)
2
>>>下一個(x)
3
>>>下一個(x)
4
>>>下一個(x)
追溯(最近一次通話):
<模塊>中第1行的文件“ ”
StopIteration
內置函數next()僅調用對象.next()函數,該函數是“迭代協議”的一部分,可以在所有迭代器上找到。您可以手動使用next()函數(以及迭代協議的其他部分)來實現奇特的事情,通常是以犧牲可讀性為代價的,因此請避免這樣做...
細節
通常,大多數人不會關心以下區別,並且可能想在這裡停止閱讀。
用Python來說,可迭代是“理解for循環的概念”的任何對象,例如列表[1,2,3],而迭代器是所請求的for循環的特定實例,例如[1,2,3] ,3] .__ iter __()。生成器與任何迭代器完全相同,但其編寫方式(帶有函數語法)除外。
當您從列表中請求迭代器時,它將創建一個新的迭代器。但是,當您從迭代器請求迭代器時(很少這樣做),它只會為您提供自身的副本。
因此,在極少數情況下,您可能無法執行此類操作...
> x = myRange(5)
>清單(x)
[0,1,2,3,4]
>清單(x)
[]
...然後記住生成器是迭代器;也就是說,它是一次性的。如果要重用它,則應再次調用myRange(...)。如果需要兩次使用結果,請將結果轉換為列表並將其存儲在變量x = list(myRange(5))中。那些絕對需要克隆生成器的人(例如,正在進行駭人的駭人的元編程的人)可以在絕對必要的情況下使用itertools.tee,因為可複制的迭代器Python PEP標準建議已被推遲。
|
yield關鍵字在Python中有什麼作用?
答案大綱/摘要
具有yield的函數在調用時將返回Generator。
生成器是迭代器,因為它們實現了迭代器協議,因此您可以對其進行迭代。
也可以向生成器發送信息,使它在概念上成為協程。
在Python 3中,您可以使用yield from從兩個方向的一個生成器委託另一個生成器。
(附錄對幾個答案進行了評論,包括最上面的一個,並討論了生成器中return的用法。)
發電機:
yield僅在函數定義內部合法,並且yield包含在函數定義中使其返回生成器。
生成器的想法來自具有不同實現方式的其他語言(請參見腳註1)。在Python的Generators中,代碼的執行會在收益率點凍結。調用生成器時(下面將討論方法),恢復執行,然後凍結在下一個產量。
產量提供了
實現迭代器協議的簡便方法,由以下兩種方法定義:
__iter__和下一個(Python 2)或__next__(Python 3)。這兩種方法
將對象設為可以使用Iterator Abstract Base進行類型檢查的迭代器
來自collections模塊的類。
>>> def func():
...產生“我是”
...產生“發電機!”
...
>>> type(func)#具有yield的函數仍然是一個函數
<類型'功能'>
>>> gen = func()
>>> type(gen)#但它返回一個生成器
<類型'發電機'>
>>> hasattr(gen,'__iter__')#這是可迭代的
真正
>>> hasattr(gen,'next')#並帶有.next(Python 3中的.__ next__)
True#實現迭代器協議。
生成器類型是迭代器的子類型:
>>>導入集合,類型
>>> issubclass(types.GeneratorType,collections.Iterator)
真正
並且如有必要,我們可以像這樣進行類型檢查:
>>> isinstance(gen,types.GeneratorType)
真正
>>> isinstance(gen,collections.Iterator)
真正
迭代器的一個功能是,一旦耗盡,就無法重用或重置它:
>>>列表(gen)
['我是,'發電機!']
>>>列表(gen)
[]
如果要再次使用其功能,則必須另做一個(請參見腳註2):
>>>列表(func())
['我是,'發電機!']
一個人可以通過編程產生數據,例如:
def func(an_iterable):
對於an_iterable中的項目:
產量項目
上面的簡單生成器也等效於下面的生成器-從Python 3.3開始(在Python 2中不可用),您可以使用yield from:
def func(an_iterable):
來自an_iterable的收益
但是,收益來自還允許委派給子發電機,
這將在以下有關帶有子協程的合作授權的部分中進行說明。
協程:
yield形成一個表達式,該表達式允許將數據發送到生成器中(請參見腳註3)
這是一個示例,請注意接收到的變量,該變量將指向發送到生成器的數據:
def bank_account(存款,利率):
而True:
計算的利息=利率*存入
收到=收益計算所得的利息
如果收到:
存入+ =已收到
>>> my_account = bank_account(1000,.05)
首先,我們必須使用內置函數將生成器排隊。它會
根據版本的不同,調用相應的next或__next__方法
您正在使用的Python:
>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0
現在我們可以將數據發送到生成器中。 (不發送是
與調用next相同。):
>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5
合作派遣到協程,收益來自
現在,回想一下,從收益率可在Python 3。這使我們能夠協同程序委派到subcoroutine:
def money_manager(expected_rate):
#必須從.send()接收存款值:
under_management = yield#yield沒有開始。
而True:
嘗試:
另外的投資=預期收益率*管理不足
如果額外投資:
管理欠佳+ =額外投資
除了GeneratorExit:
'''TODO:寫函數發送無人認領的資金狀態'''
提高
最後:
'''待辦事項:編寫功能以將稅收信息郵寄給客戶'''
def investment_account(已存款,經理):
'''非常簡單的委託給經理的投資賬戶模型'''
#必須讓管理員排隊:
next(manager)#<-與manager.send相同(無)
#在這裡我們將初始存款發送給經理:
manager.send(存款)
嘗試:
經理收益
除了GeneratorExit:
返回manager.close()#委託?
現在我們可以將功能委託給子生成器,並且可以使用它
由上面的發電機產生:
my_manager = money_manager(.06)
my_account = investment_account(1000,my_manager)
first_year_return = next(my_account)#-> 60.0
現在模擬向該帳戶再添加1,000,再加上該帳戶的收益(60.0):
next_year_return = my_account.send(first_year_return + 1000)
next_year_return#123.6
您可以在PEP 380中閱讀有關yield的精確語義的更多信息。
其他方法:關閉並拋出
close方法在功能上提高GeneratorExit
執行被凍結。這也會被__del__調用,因此您
可以將任何清理代碼放在您處理GeneratorExit的位置:
my_account.close()
您還可以引發可以在生成器中處理的異常
或傳播回用戶:
導入系統
嘗試:
引發ValueError
除了:
my_manager.throw(* sys.exc_info())
籌款:
追溯(最近一次通話):
<模塊>中第4行的文件“ ”
money_manager中的第6行的文件“ ”
<模塊>中第2行的文件“ ”
ValueError
結論
我相信我已經涵蓋了以下問題的各個方面:
yield關鍵字在Python中有什麼作用?
事實證明,收益很大。我確定我可以添加更多
詳盡的例子。如果您想要更多或有建設性的批評,請通過評論讓我知道
下面。
附錄:
對最佳/可接受答案的評論**
僅以列表為例,它對使可迭代的內容感到困惑。請參閱上面的參考資料,但總而言之:iterable具有返回迭代器的__iter__方法。迭代器提供.next(Python 2或.__ next __(Python 3))方法,該方法由for循環隱式調用,直到引發StopIteration,一旦這樣做,它將繼續這樣做。
然後,它使用生成器表達式來描述什麼是生成器。由於生成器只是創建迭代器的一種簡便方法,因此它只會使問題感到困惑,而我們仍未進入屈服部分。
在控制發電機的排氣中,他調用.next方法,而接下來應使用內置函數。這將是一個適當的間接層,因為他的代碼在Python 3中不起作用。
Itertools?這與產量完全無關。
沒有討論yield的方法以及Python 3中新功能的產生。最高/可接受的答案是非常不完整的答案。
對生成器表達或理解中的屈服的答案的評論。
該語法當前允許列表理解中的任何表達式。
expr_stmt:testlist_star_expr(annassign | augassign(yield_expr | testlist)|
('='(yield_expr | testlist_star_expr))*)
...
yield_expr:“ yield” [yield_arg]
yield_arg:“來自”測試|測試清單
由於yield是一種表達,因此儘管沒有特別好的用例,但有人認為它可以用於理解或生成器表達中。
CPython核心開發人員正在討論棄用其津貼。
這是郵件列表中的相關帖子:
2017年1月30日19:05,布雷特·坎農寫道:
2017年1月29日星期日,克雷格·羅德里格斯(Craig Rodrigues)在星期日寫道:
兩種方法我都可以。保持Python 3中的狀態
不好,恕我直言。
我的投票是SyntaxError,因為您沒有從中得到期望
語法。
我同意這是我們最終的明智之地,因為任何代碼
依靠當前的行為真的太聰明了
可維護的。
在到達目的地方面,我們可能需要:
3.7中的語法警告或棄用警告
2.7.x中的Py3k警告
3.8中的SyntaxError
乾杯,尼克。
-Nick Coghlan | gmail.com上的ncoghlan |澳大利亞布里斯班
此外,還有一個懸而未決的問題(10544),似乎正說明這絕不是一個好主意(PyPy,用Python編寫的Python實現,已經在發出語法警告。)
最重要的是,直到CPython的開發人員另行告訴我們為止:不要將yield放在生成器表達式或理解中。
生成器中的return語句
在Python 2中:
在生成器函數中,return語句不允許包含expression_list。在這種情況下,簡單的返回指示生成器已完成,並將導致StopIteration升高。
expression_list基本上是由逗號分隔的任意數量的表達式-本質上,在Python 2中,您可以使用return停止生成器,但不能返回值。
在Python 3中:
在生成器函數中,return語句指示生成器已完成,並將引起StopIteration升高。返回的值(如果有)用作構造StopIteration的參數,並成為StopIteration.value屬性。
腳註
提案中引用了CLU,Sather和Icon語言
向Python介紹生成器的概念。總體思路是
函數可以保持內部狀態並產生中間值
用戶按需提供數據點。這有望在性能上出眾
其他方法,包括Python線程,在某些系統上甚至不可用。
例如,這意味著xrange對象(在Python 3中為range)不是迭代器,即使它們是可迭代的,因為它們可以被重用。像列表一樣,它們的__iter__方法返回迭代器對象。
yield最初是​​作為聲明引入的,這意味著它
只能出現在代碼塊中一行的開頭。
現在yield將創建一個yield表達式。
https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt
提出此更改是為了允許用戶將數據發送到生成器中,就像
一個人可能會收到它。要發送數據,必須能夠將其分配給某物,並且
為此,聲明將行不通。
|
yield就像return一樣-它返回您告訴的任何內容(作為生成器)。不同之處在於,下次調用生成器時,執行從最後一次調用到yield語句開始。與return不同的是,在產生良率時不會清除堆棧幀,但是會將控制權轉移回調用方,因此其狀態將在下次調用函數時恢復。
就您的代碼而言,該函數get_child_candidates就像一個迭代器,因此當您擴展列表時,它一次向新列表添加一個元素。
list.extend調用迭代器,直到耗盡為止。對於您發布的代碼示例,返回一個元組並將其附加到列表中會更加清楚。
|
還有另外一件事要提到:yield的函數實際上不必終止。我寫了這樣的代碼:
def fib():
最後,cur = 0,1
而True:
屈服電流
最後,當前=當前,最後+當前
然後我可以在其他代碼中使用它:
對於fib()中的f:
如果some_condition:中斷
coolfuncs(f);
它確實有助於簡化某些問題,並使某些事情更易於使用。
|
對於那些偏愛簡單工作示例的人,請在此交互式Python會話中進行冥想:
>>> def f():
...產量1
...產量2
...產量3
...
>>> g = f()
>>>對於我在g中:
...打印(i)
...
1個
2
3
>>>對於我在g中:
...打印(i)
...
>>>#注意這次沒有打印任何內容
|
TL; DR
代替這個:
def square_list(n):
the_list = []#替換
對於x在範圍(n)中:
y = x * x
the_list.append(y)#這些
返回the_list#行
做這個:
def square_yield(n):
對於x在範圍(n)中:
y = x * x
用這個生成y#。
每當您發現自己從頭開始建立清單時,請改為提供每份清單。
這是我第一次屈服。
產量是一種含糖的說法
建立一系列的東西
相同的行為:
>>>對於square_list(4)中的square:
...打印(正方形)
...
1個
4
9
>>>對於square_yield(4)中的平方:
...打印(正方形)
...
1個
4
9
不同的行為:
收益是單次通過:您只能迭代一次。當函數具有收益時,我們稱其為生成器函數。而迭代器就是它返回的內容。這些術語在揭示。我們失去了容器的便利性,但獲得了按需計算且任意長的序列的功效。
產量是懶惰的,它推遲了計算。當調用它時,其中包含yield的函數實際上根本不會執行。它返回一個迭代器對象,該對象記住它從何處中斷。每次您在迭代器上調用next()(這在for循環中發生)時,執行都會向前推進到下一個收益。 return引發StopIteration並結束序列(這是for循環的自然結束)。
產量多才多藝。數據不必全部存儲在一起,可以一次存儲一次。它可以是無限的。
>>> def squares_all_of_them():
... x = 0
...而True:
...產量x * x
... x + = 1
...
>>>正方形= squares_all_of_them()
>>>範圍內的_:(4):
...打印(下一個(正方形))
...
1個
4
9
如果您需要多次通過並且序列不太長,只需在其上調用list()即可:
>>>列表(square_yield(4))
[0,1,4,9]
明智地選擇yield,因為兩種含義都適用:
產量—生產或提供(如在農業中)
...提供系列中的下一個數據。
屈服—讓步或放棄(如在政治權力中一樣)
...放棄CPU執行,直到迭代器前進。
|
收益為您提供發電機。
def get_odd_numbers(i):
返回範圍(1,i,2)
def yield_odd_numbers(i):
對於x在範圍(1,i,2)中:
產量x
foo = get_odd_numbers(10)
條= yield_odd_numbers(10)
富
[1、3、5、7、9]
酒吧
<生成器對象yield_odd_numbers at 0x1029c6f50>
bar.next()
1個
bar.next()
3
bar.next()
5
如您所見,在第一種情況下,foo一次將整個列表保存在內存中。包含5個元素的列表並不是什麼大問題,但是如果您想要500萬個列表,該怎麼辦?這不僅是一個巨大的內存消耗者,而且在調用函數時還花費大量時間來構建。
在第二種情況下,bar只是為您提供了一個生成器。生成器是可迭代的-這意味著您可以在for循環等中使用它,但是每個值只能被訪問一次。所有的值也不會同時存儲在存儲器中。生成器對象“記住”您上次調用它時在循環中的位置-這樣,如果您使用的是一個迭代的(例如)計數為500億,則不必計數為500億立即存儲500億個數字以進行計算。
再次,這是一個非常人為的示例,如果您真的想計數到500億,則可能會使用itertools。 :)
這是生成器最簡單的用例。如您所說,它可以用來編寫有效的排列,使用yield可以將內容推入調用堆棧,而不是使用某種堆棧變量。生成器還可以用於特殊的樹遍歷以及所有其他方式。
|
它正在返回發電機。我對Python並不是特別熟悉,但是我相信,如果您熟悉Python,它與C#的迭代器塊是一樣的東西。
關鍵思想是,編譯器/解釋器/會做一些棘手的事情,以便就調用者而言,他們可以繼續調用next(),它將繼續返回值-就像Generator方法已暫停一樣。現在顯然您不能真正地“暫停”方法,因此編譯器構建了一個狀態機,供您記住您當前所在的位置以及局部變量等的外觀。這比自己編寫迭代器要容易得多。
|
在描述如何使用生成器的許多很棒的答案中,我還沒有給出一種答案。這是編程語言理論的答案:
Python中的yield語句返回一個生成器。 Python中的生成器是一個返回延續的函數(特別是一種協程,但是延續代表了一種更通用的機制來了解發生了什麼事情)。
編程語言理論中的連續性是一種更為基礎的計算,但是由於它們很難推理而且也很難實現,因此並不經常使用。但是,關於延續是什麼的想法很簡單:只是尚未完成的計算狀態。在此狀態下,將保存變量的當前值,尚未執行的操作等。然後,在稍後的某個時刻,可以在程序中調用繼續,以便將程序的變量重置為該狀態,並執行保存的操作。
以這種更一般的形式進行的連續可以兩種方式實現。以call / cc的方式,該程序的堆棧按字面意義保存,然後在調用延續時,將還原該堆棧。
在延續傳遞樣式(CPS)中,延續只是普通的函數(僅在函數是第一類的語言中),程序員明確地對其進行管理並傳遞給子例程。以這種方式,程序狀態由閉包(以及恰好在其中編碼的變量)表示,而不是駐留在堆棧中某個位置的變量。管理控制流的函數接受連續作為參數(在CPS的某些變體中,函數可以接受多個連續),並通過簡單地調用它們並隨後返回來調用它們來操縱控制流。延續傳遞樣式的一個非常簡單的示例如下:
def save_file(文件名):
def write_file_continuation():
write_stuff_to_file(文件名)
check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)
在這個(非常簡單的)示例中,程序員保存了將文件實際寫入連續的操作(該操作可能是非常複雜的操作,需要寫很多細節),然後傳遞該連續(即,首先類關閉)到另一個進行更多處理的運算符,然後在必要時調用它。 (我在實際的GUI編程中經常使用此設計模式,這是因為它節省了我的代碼行,或更重要的是,在GUI事件觸發後管理控制流。)
在不失一般性的前提下,本文的其餘部分將連續性概念化為CPS,因為它很容易理解和閱讀。
現在讓我們談談Python中的生成器。生成器是延續的特定子類型。延續通常能夠保存計算狀態(即程序的調用堆棧),而生成器只能保存迭代器上的迭代狀態。雖然,對於發電機的某些使用情況,此定義會產生誤導。例如:
def f():
而True:
產量4
這顯然是一個合理的迭代器,其行為已得到很好的定義-每次生成器對其進行迭代時,它都會返回4(並且永遠如此)。但是在考慮迭代器時(例如,集合中的x:do_something(x)),可能不會想到原型的可迭代類型。此示例說明了生成器的功能:如果有什麼是迭代器,生成器可以保存其迭代狀態。
重申一下:連續可以保存程序堆棧的狀態,而生成器可以保存迭代的狀態。這意味著延續比生成器強大得多,但是生成器也非常簡單。它們對於語言設計人員來說更容易實現,對程序員來說也更容易使用(如果您有時間要花些時間,請嘗試閱讀並理解有關延續和call / cc的本頁)。
但是您可以輕鬆地將生成器實現(並將其概念化)為連續傳遞樣式的一種簡單的特定情況:
每當調用yield時,它都會告訴函數返回一個延續。再次調用該函數時,將從中斷處開始。因此,在偽偽代碼(即不是偽代碼,而不是代碼)中,生成器的next方法基本上如下:
Generator()類:
def __init __(self,iterable,generatorfun):
self.next_continuation = lambda:generatorfun(可迭代)
def next(self):
值,next_continuation =self.next_continuation()
self.next_continuation = next_continuation
返回值
其中yield關鍵字實際上是實際生成器函數的語法糖,基本上是這樣的:
def generatorfun(可迭代):
如果len(iterable)== 0:
提高StopIteration
其他:
返回(iterable [0],lambda:generatorfun(iterable [1:]))
請記住,這只是偽代碼,Python中生成器的實際實現更為複雜。但是,作為練習以了解發生了什麼,請嘗試使用連續傳遞樣式來實現生成器對象,而不使用yield關鍵字。
|
這是簡單語言的示例。我將提供高級人類概念與低級Python概念之間的對應關係。
我想對數字序列進行運算,但是我不想為創建該序列而煩惱自己,我只想著重於自己想做的運算。因此,我執行以下操作:
我打電話給你,告訴你我想要一個以特定方式產生的數字序列,讓您知道算法是什麼。此步驟對應於定義生成器函數,即包含yield的函數。
稍後,我告訴您,“好,準備告訴我數字的順序”。此步驟對應於調用生成器函數,該函數返回生成器對象。請注意,您還沒有告訴我任何數字。你只要拿起紙和鉛筆。
我問你,“告訴我下一個號碼”,然後你告訴我第一個號碼;之後,您等我要求您提供下一個電話號碼。記住您的位置,已經說過的電話號碼以及下一個電話號碼是您的工作。我不在乎細節。此步驟對應於在生成器對像上調用.next()。
…重複上一步,直到…
最終,您可能會走到盡頭。你不告訴我電話號碼;您只是大聲喊道:“抱馬!我做完了!沒有數字了!”此步驟對應於生成器對象結束其工作,並引發StopIteration異常。生成器函數不需要引發異常。函數結束或發出返回值時,它將自動引發。
這就是生成器的功能(包含yield的函數);它開始執行,在產生任何結果時暫停,並在詢問.next()值時從其最後一點繼續。根據設計,它與Python的迭代器協議完美契合,該協議描述瞭如何順序請求值。
迭代器協議最著名的用戶是Python中的for命令。因此,只要您執行以下操作:
對於順序項目:
序列是如上所述的列表,字符串,字典還是生成器對像都無關緊要;結果是一樣的:您從一個序列中逐個讀取項目。
請注意,定義包含yield關鍵字的函數不是創建生成器的唯一方法;這是創建一個的最簡單的方法。
有關更準確的信息,請閱讀Python文檔中有關迭代器類型,yield語句和生成器的信息。
|
儘管很多答案表明了為什麼要使用yield來創建生成器,但是yield有更多用途。創建協程非常容易,這使信息可以在兩個代碼塊之間傳遞。我不會重複任何關於使用yield來創建生成器的優秀示例。
為了幫助理解以下代碼中的yield,您可以用手指在任何具有yield的代碼中跟踪循環。每次您的手指觸碰良率時,您都必須等待下一個或要發送的輸入。調用next時,您將遍歷代碼,直到達到收益為止。收益率右側的代碼將被評估並返回給調用方……然後等待。再次調用next時,您將在代碼中執行另一個循環。但是,您會注意到,在協程中,yield也可以與send一起使用……它將從調用方將值發送到yield函數。如果給出了發送,則yield接收發送的值,然後將其吐出左側。然後,通過代碼進行的跟踪將一直進行,直到再次達到yield為止為止(最後返回值,就像調用了next一樣)。
例如:
>>> def coroutine():
...我= -1
...而True:
...我+ = 1
... val =(收益i)
... print(“收到的%s”%val)
...
>>>序列= coroutine()
>>> sequence.next()
>>> sequence.next()
沒有收到
1個
>>> sequence.send('hello')
收到你好
2
>>> sequence.close()
|
還有另一個yield用法和含義(自Python 3.3起):
的收益
從PEP 380-委託給子生成器的語法:
提出了一種語法,供生成器將其部分操作委託給另一生成器。這允許包含“ yield”的一段代碼被分解並放置在另一個生成器中。此外,允許子生成器返回一個值,並且該值可供委託生成器使用。
當一個生成器重新產生另一個生成器的值時,新語法還為優化提供了一些機會。
此外,這將引入(自Python 3.5起):
異步def new_coroutine(數據):
...
等待blocking_action()
為了避免將協程與常規生成器混淆(兩者目前都使用了產量)。
|
所有好的答案,但是對於新手來說有點困難。
我認為您已經了解了return語句。
打個比方,收益率和收益率是雙胞胎。 return表示“返回並停止”,而“ yield”表示“返回但繼續”
嘗試獲取帶有返回的num_list。
def num_list(n):
對於我在範圍(n)中:
還給我
運行:
在[5]中:num_list(3)
出[5]:0
看,您只會得到一個數字,而不是列表。 return永遠不會讓你高高興興,只執行一次就退出。
有產量
用收益替換收益:
在[10]中:def num_list(n):
...:對於範圍(n)中的i:
...:讓我
...:
在[11]中:num_list(3)
Out [11]:<位於0x10327c990的發電機對象num_list>
在[12]中:list(num_list(3))
出[12]:[0、1、2]
現在,您將贏得所有數字。
比較一次運行並停止的收益率,收益運行時間是您計劃的時間。
您可以將return解釋為其中之一,並把yield解釋為全部。這稱為可迭代。
我們可以再一步用return重寫yield語句
在[15]中:def num_list(n):
...:結果= []
...:對於範圍(n)中的i:
...:result.append(i)
...:返回結果
在[16]中:num_list(3)
出[16]:[0,1,2]
這是產量的核心。
列表返回輸出和對象收益輸出之間的區別是:
您將始終從列表對象獲取[0,1,2],但只能從“對象產量輸出”中檢索一次。因此,它具有一個新的名稱生成器對象,如Out [11]中所示:<生成器對象num_list位於0x10327c990>。
最後,作為一個隱喻,它可以:
收益率和收益率是雙胞胎
列表和發電機是雙胞胎
|
從編程的角度來看,迭代器被實現為thunk。
為了將迭代器,生成器和線程池實現為thunk,以便並發執行等,人們使用發送到具有分派器的閉包對象的消息,然後分派器對“消息”做出響應。
“下一個”是發送到閉包的消息,由“ iter”調用創建。
有很多方法可以實現此計算。我使用了變異,但是可以通過返回當前值和下一個生成器(使其參照透明)來進行這種無變異的計算。 Racket使用某些中間語言對初始程序進行一系列轉換,這種重寫之一使yield運算符可以使用更簡單的運算符轉換為某種語言。
這是使用R6RS的結構如何重寫yield的演示,但是其語義與Python相同。它是相同的計算模型,只需要更改語法即可使用Python的產量重寫它。
歡迎使用Racket v6.5.0.3。
->(定義gen
(λ(l)
(定義產量
(lambda()
(如果(為空?l)
'結束
(讓[[v [car l)))
(設置!l(cdr l))
v))))
(λ(米)
(案例m
('收益率(yield))
('init(lambda(數據)
(設置!l個數據)
'好))))))
->(定義流(gen'(1 2 3)))
->(流的收益)
1個
->(流的收益)
2
->(流的收益)
3
->(流的收益)
'結束
->((流'init)'(a b))
'好
->(流的收益)
'一種
->(流的收益)
'b
->(流的收益)
'結束
->(流的收益)
'結束
->
|
以下是一些Python示例,這些示例說明如何實際實現生成器,就像Python沒有為其提供語法糖一樣:
作為Python生成器:
從itertools導入islice
def fib_gen():
a,b = 1,1
而True:
產生一個
a,b = b,a + b
斷言[1,1,2,3,5] == list(islice(fib_gen(),5))
使用詞法閉包而不是生成器
def ftake(fnext,最後):
返回[在xrange(last)中_的[fnext())]
def fib_gen2():
#funky作用域由於python2.x解決方法
#對於python 3.x使用非本地
def _():
_.a,_。b = _.b,_。a + _.b
返回_.a
_.a,_。b = 0、1
返回_
斷言[1,1,2,3,5] == ftake(fib_gen2(),5)
使用對象閉包而不是生成器(因為ClosuresAndObjectsAreEquivalent)
類fib_gen3:
def __init __():
self.a,self.b = 1,1
def __call __(自己):
r =自我
self.a,self.b = self.b,self.a + self.b
返回r
斷言[1,1,2,3,5] == ftake(fib_gen3(),5)
|
我打算發布“閱讀Beazley的“ Python:基本參考”的第19頁,以快速了解生成器”,但是已經有許多其他人發布了不錯的描述。
另外,請注意,協程可以將yield用作生成函數的雙重用途。儘管(yield)與代碼段用法不同,但它可以用作函數中的表達式。當調用者使用send()方法向該方法發送值時,協程將一直執行,直到遇到下一個(yield)語句為止。
發電機和協程是設置數據流類型應用程序的一種很酷的方法。我認為值得了解函數中yield語句的其他用法。
|
這是一個簡單的示例:
def isPrimeNumber(n):
打印“ isPrimeNumber({})調用”。format(n)
如果n == 1:
返回False
對於範圍(2,n)中的x:
如果n%x == 0:
返回False
返回True
def素數(n = 1):
while(真):
打印“循環步驟---------------- {}”。format(n)
如果isPrimeNumber(n):產生n
n + = 1
對於質數()中的n:
如果n> 10:break
打印“寫結果{}”。format(n)
輸出:
循環步驟---------------- 1
isPrimeNumber(1)調用
循環步驟---------------- 2
isPrimeNumber(2)調用
循環步驟---------------- 3
isPrimeNumber(3)調用
寫結果3
循環步驟---------------- 4
isPrimeNumber(4)調用
循環步驟---------------- 5
isPrimeNumber(5)調用
寫結果5
循環步驟---------------- 6
isPrimeNumber(6)調用
循環步驟---------------- 7
isPrimeNumber(7)調用
寫結果7
循環步驟---------------- 8
isPrimeNumber(8)調用
循環步驟---------------- 9
isPrimeNumber(9)調用
循環步驟---------------- 10
isPrimeNumber(10)調用
循環步驟---------------- 11
isPrimeNumber(11)調用
我不是Python開發人員,但在我看來,yield保持程序流的位置,而下一個循環從“ yield”位置開始。好像它在那個位置上等待,就在那之前,在外面返回一個值,下一次繼續工作。
這似乎是一種有趣而又不錯的能力:D
|
這是產量的心理印象。
我喜歡將線程視為具有堆棧(即使未以這種方式實現)。
調用普通函數時,它將其局部變量放在堆棧上,進行一些計算,然後清除堆棧並返回。再也看不到其局部變量的值。
使用yield函數,當其代碼開始運行時(即在調用函數後,返回生成器對象,然後調用next()方法的生成器對象),它類似地將其局部變量放到堆棧上並進行一段時間的計算。但是,當它命中yield語句時,在清除堆棧的一部分並返回之前,它會對其局部變量進行快照並將其存儲在生成器對像中。它還會在代碼中寫下當前位置(即特定的yield語句)。
因此,這是生成器掛起的一種凍結函數。
隨後調用next()時,它將函數的所有物檢索到堆棧上並對其進行動畫處理。該函數從中斷處繼續進行計算,而忽略了它剛剛在冷藏庫中度過了一個永恆的事實。
比較以下示例:
def normalFunction():
返回
如果為假:
通過
def yielderFunction():
返回
如果為假:
產量12
當我們調用第二個函數時,它的行為與第一個函數非常不同。 yield語句可能無法到達,但是如果它存在於任何地方,它將改變我們正在處理的內容的性質。
>>> yielderFunction()
<生成器對象yielderFunction at 0x07742D28>
調用yielderFunction()不會運行其代碼,但是會使代碼生成一個生成器。 (為便於閱讀,使用yielder前綴命名此類名稱可能是個好主意。)
>>> gen = yielderFunction()
>>> dir(gen)
['__類__',
...
'__iter __',#返回gen本身,使其與容器統一工作
...#設置為for循環時。 (容器返回一個迭代器。)
'關',
'gi_code',
'gi_frame',
'gi_running',
'next',#運行函數主體的方法。
'發送',
'扔']
gi_code和gi_frame字段是凍結狀態的存儲位置。用dir(..)探索它們,我們可以確認上面的心理模型是可信的。
|
一個簡單的例子來了解它是什麼:yield
def f123():
對於_範圍(4):
產量1
產量2
對於我在f123()中:
打印(i)
輸出為:
1 2 1 2 1 2 1 2
|
就像每個答案所暗示的那樣,yield用於創建序列生成器。它用於動態生成一些序列。例如,在網絡上逐行讀取文件時,可以按以下方式使用yield函數:
def getNextLines():
而con.isOpen():
產生con.read()
您可以在代碼中使用它,如下所示:
對於getNextLines()中的行:
doSomeThing(行)
執行控制轉移陷阱
執行yield時,執行控制將從getNextLines()轉移到for循環。因此,每次調用getNextLines()時,都會從上次暫停的位置開始執行。
因此,簡而言之,具有以下代碼的函數
def simpleYield():
產生“第一次”
產生“第二次”
產生“第三次”
產生“現在有用的值{}”。format(12)
對於我在simpleYield()中:
打印我
將打印
“第一次”
“第二次”
“第三次”
“現在有一些有用的值12”
|
(我下面的回答僅從使用Python生成器的角度講,而不是生成器的基礎實現機制,其中涉及一些堆棧和堆操作的技巧。)
當使用yield而不是python函數中的返回值時,該函數將變成一種特殊的生成器函數。該函數將返回生成器類型的對象。 yield關鍵字是一個標誌,用於通知python編譯器專門對待此類功能。普通函數將在返回一些值後終止。但是在編譯器的幫助下,可以將generator函數視為可恢復的。也就是說,將恢復執行上下文,並且將從上次運行繼續執行。在顯式調用return之前,它將引發StopIteration異常(這也是迭代器協議的一部分),或者到達函數的結尾。我發現了很多有關生成器的參考,但是從功能編程的角度來看,這是最易理解的。
(現在,我想根據自己的理解來討論generator的基本原理,以及迭代器。我希望這可以幫助您掌握迭代器和generator的基本動機。這種概念在其他語言(例如C#)中也會出現。)
據我了解,當我們要處理一堆數據時,通常會先將數據存儲在某個地方,然後再逐一處理。但是這種幼稚的方法是有問題的。如果數據量巨大,則預先存儲它們是很昂貴的。因此,為什麼不間接存儲某種類型的元數據,而不是直接存儲數據本身,即邏輯如何計算數據。
有兩種包裝此類元數據的方法。
面向對象的方法,我們將元數據包裝為一個類。這就是實現迭代器協議的所謂迭代器(即__next __()和__iter __()方法)。這也是常見的迭代器設計模式。
在功能方法上,我們將元數據包裝為一個函數。這是
所謂的發電機功能。但實際上,返回的生成器對象仍然是IS-A迭代器,因為它也實現了迭代器協議。
無論哪種方式,都會創建一個迭代器,即某個可以為您提供所需數據的對象。 OO方法可能有點複雜。無論如何,使用哪個取決於您。
|
總而言之,yield語句將您的函數轉換為一個工廠,該工廠產生一個稱為生成器的特殊對象,該對象環繞原始函數的主體。迭代生成器後,它將執行您的函數,直到達到下一個yield為止,然後掛起執行並評估傳遞給yield的值。它將在每次迭代中重複此過程,直到執行路徑退出函數為止。例如,
def simple_generator():
產生一個
產生“兩個”
產生“三”
對於我在simple_generator()中:
打印我
簡單地輸出
一
二
三
動力來自將生成器與計算序列的循環配合使用,生成器每次執行循環都會停止,以“屈服”下一個計算結果,這樣就可以即時計算列表,好處是存儲保存用於特別大的計算
假設您要創建自己的範圍函數以產生可迭代的數字範圍,則可以這樣做,
def myRangeNaive(i):
n = 0
範圍= []
而n >> fib()
<生成器對象fib位於0x7fa38394e3b8>
這是因為yield的存在向Python發出信號,要求您創建一個生成器,即一個按需生成值的對象。
那麼,如何生成這些值?可以直接使用下一步的內置函數來完成此操作,也可以通過將其提供給使用值的構造來間接完成此操作。
使用內置的next()函數,您可以直接調用.next / __ next__,強制生成器生成一個值:
>>> g = fib()
>>>下一個(g)
1個
>>>下一個(g)
1個
>>>下一個(g)
2
>>>下一個(g)
3
>>>下一個(g)
5
間接地,如果將fib提供給for循環,列表初始化程序,元組初始化程序或其他任何期望對像生成/產生值的對象,則將“消耗”生成器,直到不能再產生更多值為止(它返回):
結果= []
對於我在fib(30)中:#消耗fib
results.append(i)
#也可以用
結果= list(fib(30))#消耗fib
同樣,使用元組初始化程序:
>>>元組(fib(5))#消耗fib
(1、2、3、5)
生成器在延遲方面與功能有所不同。它通過保持其本地狀態並允許您在需要時恢復來實現此目的。
首次調用fib時:
f = fib()
Python編譯函數,遇到yield關鍵字,然後簡單地將生成器對象返回給您。看起來不是很有幫助。
然後,當您請求它直接或間接生成第一個值時,它將執行找到的所有語句,直到遇到yield為止,然後將您提供的值返回給yield並暫停。為了更好地說明這一點,讓我們使用一些打印調用(如果在Python 2上,請用打印“文本”代替):
def yielder(值):
“”“這是一個無限生成器。只能在其上使用next
而1:
打印(“我將為您創造價值”)
print(“然後我會停一會兒”)
產量值
打印(“讓我們再次遍歷它。”)
現在,輸入REPL:
>>> gen = yielder(“ Hello,yield!”)
您現在有了一個生成器對象,等待一個命令來生成一個值。接下來使用並查看打印出來的內容:
>>> next(gen)#運行直到找到產量
我將為您創造價值
那我停一會兒
“你好,屈服!”
未報價的結果是所打印的內容。引用的結果是從yield返回的結果。現在再次撥打電話:
>>> next(gen)#從產量繼續運行並再次運行
讓我們再經歷一次。
我將為您創造價值
那我停一會兒
“你好,屈服!”
生成器記住它已停頓在yield值上,然後從那裡恢復。打印下一條消息,並再次執行搜索yield語句以使其暫停的消息(由於while循環)。
|
1個
2
下一個
高度活躍的問題。贏得10個聲譽才能回答這個問題。信譽要求有助於保護該問題免受垃圾郵件和非答復活動的侵害。
不是您要找的答案?瀏覽其他問題標記為python的迭代器生成器產生協程或詢問您自己的問題。